In [1]:
from IPython.core.display import HTML
def css_styling():
    styles = "<style>"+ open("styles/custom.css", "r").read()+"</style>"
    return HTML(styles)
css_styling()
Out[1]:

Car CAD Code!

The code that was used to generate the CAD files used in the 3D printer

⏬This is just importing everything we need and setting up some output look and feel

In [2]:
from cProfile import label
import os
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"
os.add_dll_directory("C:\\Users\\Someone\\micromamba\\envs\\oshoe\\Lib\\site-packages\\win32\\")
os.add_dll_directory("c:/Users/Someone/micromamba/envs/oshoe/lib/site-packages/cadquery/")
from sympy.physics.units.quantities import Quantity
from IPython.display import Image
from IPython.display import display
from IPython.core.interactiveshell import InteractiveShell
from nbconvert.preprocessors import ExecutePreprocessor
import matplotlib.pyplot as plt
import numpy as np
from re import T
import cadquery as cq
from cq_warehouse.sprocket import Sprocket
import cq_warehouse as cqw
from cq_warehouse.fastener import *
import cq_warehouse.extensions
from cadquery import exporters
import matplotlib.pyplot as plt
from IPython.display import Image
from IPython.display import display
from IPython.core.interactiveshell import InteractiveShell
from nbconvert.preprocessors import ExecutePreprocessor
from IPython.core.display import HTML
import numpy as np
from typing import Union
from build123d import *
from cq_gears import (SpurGear, HerringboneGear, RackGear, HerringboneRackGear,
                      PlanetaryGearset, HerringbonePlanetaryGearset,
                      BevelGearPair, Worm, CrossedGearPair, HyperbolicGearPair,
                      RingGear, HerringboneRingGear, BevelGear)
from cq_gears import SpurGear, HerringboneGear, RackGear, HerringboneRackGear
from math import pi
import copy
plt.rcParams['figure.figsize'] = [9, 9]
InteractiveShell.ast_node_interactivity = "all"
np.random.default_rng()
np.set_printoptions(suppress=True)
def cq2b3d(cq_obj):
    """Convert a CADQuery solid to a Build123d solid.  This function is because Build123D is based on another API, CADQuery, and these bolt and gear APIs are written in CADQuery. In order to make them work in this API, you need to run this command
    Args:
        cq_obj (solid): The CADQuery solid to be converted to Build123d.  
    Returns:
        solid: a Build 123d solid
    """
    solid = Solid.make_box(1, 1, 1)
    solid.wrapped = cq_obj.wrapped or cq_obj.val().wrapped
    return solid
breadboard =import_step('64HalfsizeBreadboard.step')
type(breadboard)
Out[2]:
build123d.topology.Compound

We will primaraly be using the Build123D Python API ( https://build123d.readthedocs.io/en/latest/index.html ) to create our models. Although it may seem a little daunting at first glance, it allows you to create complex things quickly, and tweak parameters easily, once you get the hang of things. And best of all, it's free and open source!

🔽 This is a function that makes a size accurate model of the motor to be used out of a bunch of cylinders - we will need to modify this to fit the motors you will be using. The * symbol has many different uses in Python, depending on context, in this case, the 'Pos()' function is 'multiplied' by a 'Cylinder() to place it in the X,Y,Z Position related to Pos(X,Y,Z). The things inbetween the () are what is known as parameters.

After the Pos() and Cylinder() functions on every line but the last is a '\'. This is simply to demark that we are not done with the line, but we want to continue on to the next line in defining the variable (i.e. the 'motor' variable). Make sure you don't put it at the end of the last line, because then it will give an error that can be hard to deceiver. Next up is a + operator. What that does is adds the previous shape (part) to the next shape (part) so that it becomes an assembly of parts. It is kind of like how Legos work, you add basic shapes together to make something more complex. The opposite, the - operator, takes the previous part and gets rid of wherever it coincides (i.e. is in the same place) as the following object. This is what is know as a "Boolean" operation

In [3]:
def makeMotor():
    """Make a motor
    Returns:
        Part: a generic motor part object for placement in robots
    """
    alignMotor = (Align.CENTER, Align.CENTER, Align.MIN)
    motor = Pos(0, 0, 0) * Cylinder(radius=.75, height=40, align=alignMotor)\
        + Pos(0, 0, 10) * Cylinder(radius=3.475, height=2, align=alignMotor)\
        + Pos(0, 0, 12) * Cylinder(radius=8.5, height=3, align=alignMotor)\
        + ((Pos(0, 0, 15) * Cylinder(radius=10, height=26, align=alignMotor)
            + Pos(0, 0, 41) * Cylinder(radius=4, height=1, align=alignMotor))
           - Pos(0, 0, 0) * Cylinder(radius=.75, height=44, align=alignMotor))
    return motor
frontMotor = makeMotor()
rearMotor=makeMotor()
###We display a part by just typing it's name on a new line###
frontMotor
Out[3]:

🔼This is the resulting 3D model, you can orbit the view around to see all sides with your left mouse button, scale things using a mouse wheel, or drag them around using the mouse wheel and left button. We are not going to be printing this, it is just for reference

🔽This is a printed part to hold the front motor together, each primitive object has a .transformed((90,0,0),(12.5,0,30)) modifier. The first set of 3 numbers represent the amount of rotation in degrees (º) and the last set represent the distance from the original location.

In [4]:
mHolder=Cylinder(12,25,align=(Align.CENTER, Align.CENTER, Align.MIN)).transformed((0,0,0),(0,0,10))-makeMotor()\
    +(Box(30,12,25,align=(Align.CENTER, Align.CENTER, Align.MIN)).transformed((0,0,0),(0,0,10))\
    -Box(20,12,25,align=(Align.CENTER, Align.CENTER, Align.MIN)).transformed((0,0,0),(0,0,10)))\
    -Box(30,3,25,align=(Align.CENTER, Align.CENTER, Align.MIN)).transformed((0,0,0),(0,0,10))\
    +Box(10,5,5,align=(Align.CENTER, Align.CENTER, Align.MIN)).transformed((0,0,-45),(-10,10,10))\
    -Cylinder(1.2,12,align=(Align.CENTER, Align.CENTER, Align.CENTER)).transformed((90,0,0),(12.5,0,30))\
    -Cylinder(1.2,12,align=(Align.CENTER, Align.CENTER, Align.CENTER)).transformed((90,0,0),(-12.5,0,30))\
    -Cylinder(1.2,12,align=(Align.CENTER, Align.CENTER, Align.CENTER)).transformed((90,0,0),(12.5,0,20))\
    -Cylinder(1.2,12,align=(Align.CENTER, Align.CENTER, Align.CENTER)).transformed((90,0,0),(-12.5,0,20))\
    -Cylinder(1.2,12,align=(Align.CENTER,Align.CENTER, Align.MIN)).transformed((0,0,-45),(-11.5,11.5,10))
mHolder
mHolderHoll=Cylinder(1.2,4,align=(Align.CENTER,Align.CENTER, Align.MIN)).transformed((0,0,-45),(-11.5,11.5,6))
Out[4]:

🔽 These are some bolts that I was intending to include as reference, but they take forever to render and because we are not going to print them, I am not including them in anything.

In [5]:
# boltM1612 = SocketHeadCapScrew(size='M1.6-0.35', length=12,
#                           fastener_type='iso4762', simple=False)
# boltM1612 = cq2b3d(boltM1612)  #This function is because Build123D is based on another API, CADQuery and these bolt and gear APIs are written in CADQuery. In order to make them work in this API, you need to run this command
# boltM235 = SocketHeadCapScrew(size='M2-0.4', length=25,
#                           fastener_type='iso4762', simple=False)
# boltM235=cq2b3d(boltM235)
boltM1620 = SocketHeadCapScrew(size='M1.6-0.35', length=20,
                          fastener_type='iso4762', simple=False)
boltM1620=cq2b3d(boltM1620)
boltM1620
Out[5]:

🔽 Here, we define some gears. Gears are really amazing inventions. The gears we will be using are called "Spur Gears" that use what is known as "Involute" angles for the teeth. This is a mathematical formula that lets you mesh a gear with any number of teeth with any other gear with any number of teeth and they will mesh well, as long as they are both using the same scaling factor. An involute is basically what you would get if you pictured a pen at the end of a spiraling curve that traced the end of an imaginary taut string, unwinding itself from that stationary circle called the base circle. If you were to do this, you would get a triangle wave projected on the circumference of a circle. This wave repesents the position of the point of contact between the two gears. Wikimedia has a lot more information on the subject. We will be using this addon to create the gears: https://github.com/meadiode/cq_gears

https://en.wikipedia.org/wiki/Gear
Gear Terminology An animation of two involute gears turning together
We also need to do a little math to have the position of the gears, axel and motor be in line and work. To do this, we first need to find the dimensions of the pitch cycle (refer to left image) for motor and axil gears. This will be the distance between the two gear axes. Then, for the rear gears, because they are not at 90º angles from the ground, we need to do a little bit of math to figure out their X and Y coordinates. The `np.` before the math expression is short for NumPi, a powerful python math extention.

$X\ offset=\ 70+Difference Between The Gears*cos(-50º)$
$Y\ offset=\ 11.5+Difference Between The Gears*sin(-50º)$

In [6]:
### Larger gear for changing rotation speed###
ratioGear = SpurGear(module=.5, teeth_number=43, width=2, bore_d=1.8)
rg_pitchR = ratioGear.r0  # pitch radius
ratioGear = cq2b3d(ratioGear.build())
### Smaller Gear inside larger gear###
smallGear = SpurGear(module=.5, teeth_number=17,width=4, bore_d=1.8)
sg_pitchR=smallGear.r0
smallGear=cq2b3d(smallGear.build())
### Front Motor Gear###
motorGear = SpurGear(module=.5, teeth_number=13, width=4, bore_d=1.6)
mg_pitchR = motorGear.r0  # pitch radius
motorGear = cq2b3d(motorGear.build())
### Back Wheel Gear###
bMotorGear = SpurGear(module=.5, teeth_number=67, width=4, bore_d=2.5)
bg_pitchR = bMotorGear.r0  # pitch radius
bMotorGear = cq2b3d(bMotorGear.build())
### Back Motor Gear###
bWMotorGear = SpurGear(module=.5, teeth_number=13, width=4, bore_d=1.5)
bWg_pitchR = bWMotorGear.r0  # pitch radius
bWg_Width = bWMotorGear.ra
bWMotorGear = cq2b3d(bWMotorGear.build())
bWMotorGear
bMotorGear
difference_btwn_M_W_Gears=(bg_pitchR+bWg_pitchR)
difference_between_closer_gears = ((rg_pitchR) + (mg_pitchR))
rearBGearRadAtAngleX=70+difference_btwn_M_W_Gears*np.cos(np.deg2rad(-50))
rearBGearRadAtAngleY=11.5+difference_btwn_M_W_Gears*np.sin(np.deg2rad(-50))
Out[6]:
Out[6]:

🔽To control the steering, a simple gear won't work because we want the front wheels to each pivot around their center like a real car and not around the center of the car. Therefore, there needs to be some leeway in how the connecting rod and the gears connect. This is accomplished using a cylinder in the center of the rod that is just into a slot so that it can move a bit but still be pushed by the gears. The way the code works, I needed to create a gear with some of the teeth removed and partial cylinder with the same dimensions and use the second circle to remove the part of the gear without teeth.

In [7]:
turnGear=SpurGear(module=.5,teeth_number=61,width=4,bore_d=4,missing_teeth=(4,56))
tg_pitchR=turnGear.r0
difference_between_farther_gears=sg_pitchR+tg_pitchR
turnGear=cq2b3d(turnGear.build()).transformed((0,0,0),(0,0,0))
#For some reason this var is treaded as a 2D object unless we add an empty Part()
turnGear=Part()+turnGear-Cylinder(radius=tg_pitchR,height=8,arc_size=295
                                  ).transformed((0,0,32),(0,0,0))\
    +Box(length=5.5+tg_pitchR/2,width=6,height=4,align=(Align.MAX,Align.CENTER, Align.MIN)).transformed((0,0,0),(difference_between_farther_gears/2,0, 0))\
    - Box(length=4.5,width=2.5,height=8,align=(Align.MAX, Align.CENTER, Align.MIN)).transformed((0,0,0),((difference_between_farther_gears/2)-2,0, 0))\
    -Cylinder(1.2,10).transformed((0,0,0),(0,0, 0))
turnGear
Out[7]:

This is the connecting rod between the two front gears. It defines a polygon based on a set of (X,Y) coordinates; the (…,…) is what is called a tuple. Basically, a tuple is very similar to a list, which is represented by […,…]. Both are a collection of variables, numbers, strings, ex. that can be operated on using the same functions. You can even make lists of tuples, as I did below. The one big difference is that once defined, tuples cannot be modified. To use coding jargon, they are 'immutable' (which basically means the same thing). You can learn a ton more about tuple and lists here: https://realpython.com/python-lists-tuples/

This polygon is then extruded, or converted from 2D to 3D. Part of this new structure is then cut out using boxes. Then a separate object is created that is scaled and will be used to cut out a part of the chassis to make room for the connecting rod to turn.

In [8]:
turningConnectorDim=[(4,43),(0,43),(0,20),(8,10),(8,-10),(0,-20),(0,-43),(4,-43),(4,-20),(12,-10),(12,10),(4,20)]
rubberBandHolderDim=[(0,0,0),(0,0,-2),(0,2,-2),(0,3,0)]
rubberBandHolder=extrude(Polygon(*rubberBandHolderDim),4)
turningConnector=extrude(Polygon(*turningConnectorDim),4
                         ).transformed((0,0,0),(-68-difference_between_farther_gears,0, 0))\
    -Box(10,15,4,align=(Align.MIN, Align.CENTER, Align.CENTER)).transformed((0,0,0),(-66-difference_between_farther_gears,0, 0))\
    -Box(4,7,1.5,align=(Align.MAX, Align.MAX, Align.MIN)).transformed((0,0,0),(-70-difference_between_farther_gears,43, 0))\
    -Box(4,7,1.5,align=(Align.MAX, Align.MIN, Align.MIN)).transformed((0,0,0),(-70-difference_between_farther_gears,-43, 0))\
    -Cylinder(1.2,8).transformed((0,0,0),(-72-difference_between_farther_gears,40, 0))\
    -Cylinder(1.2,8).transformed((0,0,0),(-72-difference_between_farther_gears,-40, 0))\
    -rubberBandHolder.mirror(Plane.XZ).transformed((0,0,0),(-74-difference_between_farther_gears,28, 2))\
    -rubberBandHolder.transformed((0,0,0),(-74-difference_between_farther_gears,-28,2))
turningRoom=turningConnector.scale(1.5)
turningConnector=turningConnector+Cylinder(1,2,align=(Align.CENTER,Align.CENTER,Align.MIN)).transformed((0,0,0),(-64-difference_between_farther_gears,0, 0))
turningConnector
Out[8]:

This is how the rear motor is secured. The rMotorAssembWithoutMotor is used to export only the connecting pieces without the motor because we aren't going to be printing the motor.

In [9]:
rMotorAssemb=Box(30,4,23,align=(Align.CENTER,Align.CENTER,Align.MIN)).transformed((0,0,0), (0,-10,1.5))\
    + Box(30,4,23,align=(Align.CENTER,Align.CENTER,Align.MIN)).transformed((0,0,0), (0,-23,1.5))\
    -Cylinder(1.2,40,align=(Align.CENTER,Align.CENTER,Align.MIN)).transformed((0,0,0), (12.5,-23,1.5))\
    -Cylinder(1.2,40,align=(Align.CENTER,Align.CENTER,Align.MIN)).transformed((0,0,0), (-12.5,-23,1.5))\
    -Cylinder(1.2,40,align=(Align.CENTER,Align.CENTER,Align.MIN)).transformed((0,0,0), (12.5,-10,1.5))\
    -Cylinder(1.2,40,align=(Align.CENTER,Align.CENTER,Align.MIN)).transformed((0,0,0), (-12.5,-10,1.5))\
    - rearMotor.transformed((90,0,0),(0,10,11.5))\
    -Box(30,40,12.5,align=(Align.CENTER,Align.CENTER,Align.MAX)).transformed((0,0,0),(0,-22,11.5))
rMotorAssembWithoutMotor=rMotorAssemb
rMotorAssemb=rMotorAssemb+rearMotor.transformed((90,0,0),(0,10,11.5))
rMotorAssemb
Out[9]:

🔽 This is where the wheel meets with the chassis and the pivot point

In [10]:
frontAxleDim=[(0,0),(10,25),(10,0)]
frontAxle=extrude(Polygon(*frontAxleDim,align=(Align.MIN,Align.MIN)),2)
frontAxle=fillet(frontAxle.edges().filter_by(Axis.Z)[1],2.6)\
    -Cylinder(1.2,6).transformed((0,0,0),(7.5,10,-1.5))
frontAxle
###Actual Front Axle and other turning thing###
actualAxle=Box(4,4,11,align=(Align.CENTER, Align.CENTER, Align.CENTER)).transformed((0,0,0,),(0,0,-2))\
    +Box(8.5,4,3,align=(Align.MAX, Align.CENTER, Align.CENTER)).transformed((0,0,0),(0,0,0))\
    -Cylinder(1.2,10,align=(Align.MIN, Align.CENTER, Align.CENTER)).transformed((0,0,0),((-difference_between_farther_gears/2)+2.25,0,0))\
    +Cylinder(2,10,align=(Align.MIN, Align.MIN, Align.CENTER)).transformed((90,0,0),(-2,-7,-2))\
    -Cylinder(1.2,20).transformed((0,0,0),(0,0,0))
actualAxle
Out[10]:
Out[10]:

🔽This is the connection that secures the rear axel to the chassis

In [11]:
rAxle=Cylinder(1.4,90)
yHeight=np.abs(rearBGearRadAtAngleY*2-1.5)
rAxSupport=Box(13,yHeight,5).transformed((90,0,0),(rearBGearRadAtAngleX,0,rearBGearRadAtAngleY))-Cylinder(1.6,8).transformed((90,0,0),(rearBGearRadAtAngleX,0,rearBGearRadAtAngleY))
rAxSupport=rAxSupport-Cylinder(1.2,18).transformed((0,0,0),(rearBGearRadAtAngleX+4.5,0,rearBGearRadAtAngleY))-Cylinder(1.2,18).transformed((0,0,0),(rearBGearRadAtAngleX-4.5,0,rearBGearRadAtAngleY))
rAxSupportHoles=Cylinder(1.2,12).transformed((0,0,0),(rearBGearRadAtAngleX+4.5,0,rearBGearRadAtAngleY))+Cylinder(1.2,12).transformed((0,0,0),(rearBGearRadAtAngleX-4.5,0,rearBGearRadAtAngleY))
rAxSupport
Out[11]:

🔽 This is where we define the wheel

In [12]:
###Wheel###
hubCutout=Cylinder(20,5,arc_size=35,align=(Align.MIN,Align.MIN,Align.MIN))
wheel=Cylinder(25,5,align=(Align.CENTER,Align.CENTER,Align.MIN))
wheel=wheel\
    -Cylinder(22,4,align=(Align.CENTER,Align.CENTER,Align.MIN)).transformed((0,0,0),(0,0,1))\
    -copy.copy(hubCutout).transformed((0,0,0),(0,0,0))\
    -copy.copy(hubCutout).transformed((0,0,90),(0,0,0))\
    -copy.copy(hubCutout).transformed((0,0,180),(0,0,0))\
    -copy.copy(hubCutout).transformed((0,0,270),(0,0,0))\
    +Cylinder(2.5,5,align=(Align.CENTER,Align.CENTER,Align.MIN))\
    +Box(2,22,5,align=(Align.CENTER,Align.MIN,Align.MIN))\
    +Box(2,22,5,align=(Align.CENTER,Align.MIN,Align.MIN)).transformed((0,0,90),(0,0,0))\
    +Box(2,22,5,align=(Align.CENTER,Align.MIN,Align.MIN)).transformed((0,0,180),(0,0,0))\
    +Box(2,22,5,align=(Align.CENTER,Align.MIN,Align.MIN)).transformed((0,0,270),(0,0,0))
frontWheel=wheel+Cylinder(3,5,align=(Align.CENTER,Align.CENTER,Align.MIN))\
    -Cylinder(2.2,5,align=(Align.CENTER,Align.CENTER,Align.MIN))
backWheel=wheel+Cylinder(3,5,align=(Align.CENTER,Align.CENTER,Align.MIN))\
    -Cylinder(1.2,5,align=(Align.CENTER,Align.CENTER,Align.MIN))
frontWheel
Out[12]:

🔽 Finally, we put everything together and make the main chassis.

In [13]:
alignBox = (Align.CENTER, Align.CENTER, Align.MIN)
holes = [
         Cylinder(1.2, 4).transformed(
             (0, 0, 0), (-70, difference_between_closer_gears, 0)),
         Cylinder(1.2,40).transformed((0,0,0), (82.5,-23,1.5)),
         Cylinder(1.2,40).transformed((0,0,0), (57.5,-23,1.5)),
         Cylinder(1.2,40).transformed((0,0,0), (82.5,-10,1.5)),
         Cylinder(1.2,40).transformed((0,0,0), (57.5,-10,1.5))]
chassis =Box(length=218, width=63, height=7.5, align=alignBox).transformed((180,0,0),(0,0,1.5))
chassis=fillet(chassis.edges().filter_by(Axis.Z),15)
bb=Box(length=215,width=60,height=6,align=alignBox).transformed((180,0,0),(0,0,0))
chassis=chassis-fillet(bb.edges().filter_by(Axis.Z),15)
chassis=chassis\
    - holes\
    - Cylinder(1.2, 20).transformed((0, 0, 0), (-70, 0, -6.5))\
    - Cylinder(1.25,20).transformed((0,0,0),(-70-difference_between_farther_gears,0, -7))\
    - turningRoom.transformed((0,0,0),(45.75,0,-6))\
    -rAxle.transformed((90,0,0),(rearBGearRadAtAngleX,2.5,rearBGearRadAtAngleY))\
    - Cylinder(1.2,20).transformed((0,0,0),(-70-difference_between_farther_gears,0, -7))\
    -rAxSupportHoles.transformed((0,0,0),(0,16.5,0))\
    -rAxSupportHoles.transformed((0,0,0),(0,-16.5,0))\
    -Box(35,6,10).transformed((0,0,0), (rearBGearRadAtAngleX,2,1.5))\
    + extrude(Plane(chassis.faces().sort_by(Axis.Z)[0])*Rot(x=0,z=90)*Text("UMass",font_size=12,align=(Align.CENTER,Align.CENTER,Align.MIN)),.3).transformed((0,0,0),(-15,0,3))\
    + extrude(Plane(chassis.faces().sort_by(Axis.Z)[0])*Rot(x=0,z=90)*Text("SENGI",font_size=10,align=(Align.CENTER,Align.CENTER,Align.MIN)),.3).transformed((0,0,0),(-5,0,3))\
    + extrude(Plane(chassis.faces().sort_by(Axis.Z)[0])*Rot(x=0,z=90)*Text("2023",font_size=10,align=(Align.CENTER,Align.CENTER,Align.MIN)),.6).transformed((0,0,0),(5,0,3))\
    +frontAxle.transformed((0,0,180),(-58-difference_between_farther_gears,-30,1.5))\
    +frontAxle.mirror(Plane.XZ).transformed((0,0,180),(-58-difference_between_farther_gears,30,1.5))\
    - mHolderHoll.transformed((0, 0, -90), (-70, difference_between_closer_gears, -8))\
    + mHolder.transformed((0, 0, -90), (-70, difference_between_closer_gears, -8))\
    + frontMotor.transformed((0, 0, 0), (-70, difference_between_closer_gears, -8))\
    + rMotorAssemb.transformed((0,0,0), (70,0,0))\
    + motorGear.transformed((0,0,0),(-70, difference_between_closer_gears, -4))\
    + ratioGear.transformed((0,0,0),(-70, 0, -2))\
    + smallGear.transformed((0,0,0),(-70, 0, -6))\
    + turnGear.transformed((0, 0, 0), (-70-difference_between_farther_gears,0, -6))\
    +turningConnector.transformed((0,0,0),(0,0,-4))\
    +actualAxle.transformed((0,0,0),(-65.5-difference_between_farther_gears,-40,-3))\
    +actualAxle.mirror(Plane.XZ).transformed((0,0,0),(-65.5-difference_between_farther_gears,40,-3))\
    +bWMotorGear.transformed((90,0,0),(70,4,11.5))\
    +bMotorGear.transformed((90,0,0),(rearBGearRadAtAngleX,4,rearBGearRadAtAngleY))\
    +rAxle.transformed((90,0,0),(rearBGearRadAtAngleX,0,rearBGearRadAtAngleY))\
    +rAxSupport.transformed((0,0,0),(0,16.5,0))\
    +rAxSupport.transformed((0,0,0),(0,-16.5,0))\
    +copy.copy(backWheel).transformed((90,0,0),(rearBGearRadAtAngleX,45,rearBGearRadAtAngleY))\
    +copy.copy(backWheel).transformed((-90,0,0),(rearBGearRadAtAngleX,-45,rearBGearRadAtAngleY))\
    +copy.copy(frontWheel).transformed((90,0,0),(-65.5-difference_between_farther_gears,50,-3))\
    +copy.copy(frontWheel).transformed((-90,0,0),(-65.5-difference_between_farther_gears,-50,-3))
chassis
Out[13]:

🔽 Because we are not going to be able to print the entire car in pre-assembled form, we need to export each individual component. We also need to create a new chassis object that contains only the parts intended to be printed. The export lines are commented out so that we don't keep replacing the already exported parts.

In [14]:
chassisExp =Box(length=218, width=63, height=7.5, align=alignBox).transformed((180,0,0),(0,0,1.5))
chassisExp=fillet(chassisExp.edges().filter_by(Axis.Z),15)
bb=Box(length=215,width=60,height=6,align=alignBox).transformed((180,0,0),(0,0,0))
chassisExp=chassisExp-fillet(bb.edges().filter_by(Axis.Z),15)
chassisExp=chassisExp\
    - holes\
    - Cylinder(1.2, 20).transformed((0, 0, 0), (-70, 0, -6.5))\
    - Cylinder(1.25,20).transformed((0,0,0),(-70-difference_between_farther_gears,0, -7))\
    - turningRoom.transformed((0,0,0),(45.75,0,-6))\
    -rAxle.transformed((90,0,0),(rearBGearRadAtAngleX,2.5,rearBGearRadAtAngleY))\
    - Cylinder(1.2,20).transformed((0,0,0),(-70-difference_between_farther_gears,0, -7))\
    -rAxSupportHoles.transformed((0,0,0),(0,16.5,0))\
    -rAxSupportHoles.transformed((0,0,0),(0,-16.5,0))\
    -Box(35,6,10).transformed((0,0,0), (rearBGearRadAtAngleX,2,1.5))\
    + extrude(Plane(chassisExp.faces().sort_by(Axis.Z)[0])*Rot(x=0,z=90)*Text("UMass",font_size=12,align=(Align.CENTER,Align.CENTER,Align.MIN)),.3).transformed((0,0,0),(-15,0,3))\
    + extrude(Plane(chassisExp.faces().sort_by(Axis.Z)[0])*Rot(x=0,z=90)*Text("SENGI",font_size=10,align=(Align.CENTER,Align.CENTER,Align.MIN)),.3).transformed((0,0,0),(-5,0,3))\
    + extrude(Plane(chassisExp.faces().sort_by(Axis.Z)[0])*Rot(x=0,z=90)*Text("2023",font_size=10,align=(Align.CENTER,Align.CENTER,Align.MIN)),.6).transformed((0,0,0),(5,0,3))
chassisExp
# bWMotorGear.export_stl('bWMGear2023-06-26-1521.stl')
# rMotorAssembWithoutMotor.export_stl('rMotorAssemb2023-06-07-1405.stl')
# motorGear.export_stl('motorGear2023-06-26-1521.stl')
# (ratioGear.transformed((0,0,0),(0, 0, -2))+smallGear.transformed((0,0,0),(0, 0, -6))).export_stl('ratioGear2023-06-26-1521.stl')
# turnGear.export_stl('turningGear2023-06-26-1521.stl')
# turningConnector.export_stl('turningConnector2023-06-26-1521.stl')
# axel1=actualAxle
# axle2=actualAxle.mirror(Plane.XZ)
# axel1.export_stl('axle1_2023-06-26-1521.stl')
# axle2.export_stl('axle2_2023-06-26-1521.stl')
# bMotorGear.export_stl('bMotorGear2023-06-26-1521.stl')
# bWMotorGear.export_stl('bWMGear2023-06-26-1521.stl')
# frontWheel.export_stl('frontWheel2023-06-26-1521.stl')
# backWheel.export_stl('backWheel2023-06-26-1521.stl')
# mHolder.export_stl('mHolder2023-06-26-1521.stl')
# chassisExp.export_stl('chassis2023-06-26-1521.stl')
# rAxSupport.export_stl('chassisExp2023-06-26-1521.stl')
Out[14]:
In [ ]: